package exsun.jt808.data.media.operate.flv;

import exsun.jt808.data.media.operate.util.Int2ByteArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FlvContext {
    private final static Logger logger = LoggerFactory.getLogger(FlvContext.class);
    byte[] _FlvHeader;
    byte[] _pSPS,_pPPS;
    int _nSPSSize, _nPPSSize;
    int _bWriteAVCSeqHeader;
    int _nPrevTagSize;
    int _nStreamID;
    int _nTimeStamp;
    long _nFirstTimeStamp;


    byte[] _pAudioSpecificConfig;
    int _nAudioConfigSize;
    int _aacProfile;
    int _sampleRateIndex;
    int _channelConfig;
    int _bWriteAACSeqHeader;
    int _bHaveAudio, _bHaveVideo;

    int nTimeStamp;
    byte[] flvContentBuf;
    int flvContentSize;

    public enum EnumType {
        VIDEO_H264, AUDIO_AAC, VIDEO_H264_HEADER, AUDIO_AAC_HEADER,NONE;
    }
    public FlvContext(boolean video, boolean audio) {
        this._pSPS = null;
        this._pPPS = null;
        this._nSPSSize = 0;
        this._nPPSSize = 0;
        this._bWriteAVCSeqHeader=0;
        this._nPrevTagSize=0;
        this._nStreamID=0;
        this._nTimeStamp = 0;
        this._nFirstTimeStamp = 0;

        this._pAudioSpecificConfig=null;
        this._nAudioConfigSize=0;
        this._aacProfile=0;
        this._sampleRateIndex=0;
        this._channelConfig=0;
        this._bWriteAACSeqHeader=0;
        if (video == true) {
            this._bHaveVideo = 1;
        }
        else {
            this._bHaveVideo = 0;
        }
        if (audio == true) {
            this._bHaveAudio = 1;
        }else {
            this._bHaveAudio = 0;
        }

        this.nTimeStamp = 0;
        this.flvContentBuf=new byte[1024];
        this.flvContentSize=0;

    }

    public int get_nTimeStamp() {
        return _nTimeStamp;
    }

    public void set_nTimeStamp(int _nTimeStamp) {
        this._nTimeStamp = _nTimeStamp;
    }

    public long get_nFirstTimeStamp() {
        return _nFirstTimeStamp;
    }

    public byte[] get_FlvHeader() {
        return _FlvHeader;
    }

    public void writeFlvContent(byte[] byteBuf) {
        int newFlvContentSize = flvContentSize + byteBuf.length;//计算新内容的大小
        if(flvContentBuf==null){
            flvContentBuf=new byte[1024];
        }
        if (newFlvContentSize >= flvContentBuf.length) {//如果原来缓冲区大小加上即将写入字节大小大于原有缓冲区大小，重新申请缓冲区
            int newBufSize = ((newFlvContentSize / 1024) + 1) * 1024;//新申请1024倍数
            byte[]  newFlvContentBuf= new byte[newBufSize];
            System.arraycopy(flvContentBuf,0,newFlvContentBuf,0,flvContentSize);//拷贝原内容
            System.arraycopy(byteBuf,0,newFlvContentBuf,flvContentSize,byteBuf.length);//拷贝新写入内容
            flvContentBuf=newFlvContentBuf;
            flvContentSize += byteBuf.length;//更新内容大小
        }else {//缓冲区大小够用
            System.arraycopy(byteBuf,0,flvContentBuf,flvContentSize,byteBuf.length);//拷贝新写入内容
            flvContentSize += byteBuf.length;//更新内容大小
        }
    }
    public byte[] readFlvContent() {
        byte[] byteBuf=null;
        if(flvContentSize>0){
            byteBuf=new byte[flvContentSize];
            System.arraycopy(flvContentBuf,0, byteBuf,0,flvContentSize);//拷贝新写入内容
            flvContentBuf=null;
            flvContentSize =0;//更新内容大小
        }
        return  byteBuf;
    }
    public static byte[] GetFirstNalu(byte[] inBuf,int offset) {
        if(offset>=inBuf.length){
            return null;
        }
        int nStartPos = 0;
        int nEndPos = 0;
        int nCurrentPos=0;
        while (true)//找到h264第一帧 0x00 0x00 0x00 0x01 开头
        {
            if (inBuf[offset+nCurrentPos+0] == 0
                    && inBuf[offset+nCurrentPos+1] == 0
                    && inBuf[offset+nCurrentPos+2] == 0
                    && inBuf[offset+nCurrentPos+3] == 1) {
                nStartPos = offset+ nCurrentPos;
                break;
            }
            nCurrentPos++;
            if ((nCurrentPos+offset+3) >= inBuf.length - 4)
                return null;
        }
        nCurrentPos+=4;
        if ((nCurrentPos+offset) >= inBuf.length)
            return null;
        while (true)//找到h264第二帧 0x00 0x00 0x00 0x01 开头
        {
            if (inBuf[offset+nCurrentPos+0] == 0
                    && inBuf[offset+nCurrentPos+1] == 0
                    && inBuf[offset+nCurrentPos+2] == 0
                    && inBuf[offset+nCurrentPos+3] == 1) {
                nEndPos = offset+ nCurrentPos;
                break;
            }
            nCurrentPos++;
            if ((nCurrentPos+offset+3) >= inBuf.length)
            {
                nEndPos = inBuf.length;
                break;
            }
        }
        int frameSize = nEndPos - nStartPos;//h264第一帧的大小
        byte[] byteBuf=new byte[frameSize];
        System.arraycopy(inBuf,nStartPos,byteBuf,0,frameSize);//复制数据
        return byteBuf;
    }
    public static byte[] GetFirstAACFrame(byte[] in,int offset)
    {
        if ((in.length-offset) <= 7)
            return null;
        int a=(((in[offset+3] & 0x3) << 11)&0x07ffff);
        int b=((in[offset+4] << 3)&0x07ff);
        int c=((in[offset+5] >> 5)&0x07);
        int nFrameSize = a + b + c;//获取一帧的大小
        // System.out.println("offset: "+offset+" nFrameSize: "+nFrameSize+" a:"+a+", b:"+b+", c:"+c);
        if ((in.length-offset) < nFrameSize) {
            return null;
        }
        byte[] ret=new byte[nFrameSize];
        System.arraycopy(in,offset,ret,0,nFrameSize);//复制数据
        return ret;
    }
    public void MakeFlvHeader() {
        if(_FlvHeader==null){
            _FlvHeader=new byte[9];
        }
        _FlvHeader[0] = 'F';
        _FlvHeader[1] = 'L';
        _FlvHeader[2] = 'V';
        _FlvHeader[3] = 1;
        _FlvHeader[4] = 0x0;
        if (_bHaveVideo != 0)
            _FlvHeader[4] |= 0x01;
        if (_bHaveAudio != 0)
            _FlvHeader[4] |= 0x04;

        byte[] ret= Int2ByteArray.int2Byte4(9);
        System.arraycopy(ret,0,_FlvHeader,5,4);
        //writeFlvContent(_FlvHeader);
    }
    public void WriteH264Header(int nTimeStamp)
    {
        byte[] ret=Int2ByteArray.int2Byte4(_nPrevTagSize);
        writeFlvContent(ret);//PreviousTagSize0

        byte[] cTagType=new byte[1];
        cTagType[0]=0x09;
        writeFlvContent(cTagType);//TagType  ----- flv tags

        int nDataSize = 1 + 1 + 3 + 6 + 2 + (_nSPSSize - 4) + 1 + 2 + (_nPPSSize - 4);
        ret=Int2ByteArray.int2Byte3(nDataSize);
        writeFlvContent(ret);//DataSize

        ret=Int2ByteArray.int2Byte3(nTimeStamp);
        writeFlvContent(ret);//TimesStamp

        byte[] cTTex=new byte[1];
        cTTex[0]=(byte) ( nTimeStamp >> 24);
        writeFlvContent(cTTex);//TimesStampExtended

        ret=Int2ByteArray.int2Byte3(_nStreamID);
        writeFlvContent(ret);//StreamId

        byte[] cVideoParam=new byte[1];
        cVideoParam[0]=0x17;
        writeFlvContent(cVideoParam);//Frame type : key frame, code id : avc  ----- video tags

        byte[] cAVCPacketType=new byte[1];
        cAVCPacketType[0]=0;
        writeFlvContent(cAVCPacketType);//AVC packet type:

        ret=Int2ByteArray.int2Byte3(0);
        writeFlvContent(ret);//composition time

        byte[] a=new byte[1];
        a[0]=1;
        writeFlvContent(a);
        byte[] b=new byte[3];
        System.arraycopy(_pSPS,5,b,0,3);
        writeFlvContent(a);
        a[0]=(byte) 0xff;
        writeFlvContent(a);
        a[0]=(byte) 0xE1;
        writeFlvContent(a);

        ret=Int2ByteArray.int2Byte2(_nSPSSize - 4);
        writeFlvContent(ret);

        byte[] d=new byte[_nSPSSize - 4];
        System.arraycopy(_pSPS,4,d,0,_nSPSSize - 4);
        writeFlvContent(d);
        a[0]= 0x01;
        writeFlvContent(a);

        ret=Int2ByteArray.int2Byte2(_nPPSSize - 4);
        writeFlvContent(ret);
        byte[] e=new byte[_nPPSSize - 4];
        System.arraycopy(_pPPS,4,e,0,_nPPSSize - 4);
        writeFlvContent(e);

        _nPrevTagSize = 11 + nDataSize;
    }
    public void WriteH264Frame(byte[] in,int nTimeStamp)
    {
        byte[] pNalu=in;
        int nNaluType = pNalu[4] & 0x1f;
        if (nNaluType == 7 || nNaluType == 8)
            return;
        byte[] ret=Int2ByteArray.int2Byte4(_nPrevTagSize);
        writeFlvContent(ret);//PreviousTagSize0

        byte[] a =new byte[1];
        a[0]=0x09;
        writeFlvContent(a);//TagType  ----- flv tags

        int nDataSize;
        nDataSize = 1 + 1 + 3 + 4 + (pNalu.length - 4);
        ret=Int2ByteArray.int2Byte3(nDataSize);
        writeFlvContent(ret);//DataSize

        ret=Int2ByteArray.int2Byte3(nTimeStamp);
        writeFlvContent(ret);//TimesStamp

        a[0] = (byte) (nTimeStamp >> 24);
        writeFlvContent(a);//TimesStampExtended

        ret=Int2ByteArray.int2Byte3(_nStreamID);
        writeFlvContent(ret);//StreamId

        if (nNaluType == 5) {
            a[0] = 0x17;
            writeFlvContent(a);//Frame type : key frame, code id : avc  ----- video tags
        }else {
            a[0] = 0x27;
            writeFlvContent(a);//Frame type : inter frame, code id :  avc  ----- video tags
        }
        a[0] = 1;
        writeFlvContent(a);//AVC packet type:

        ret=Int2ByteArray.int2Byte3(0);
        writeFlvContent(ret);//composition time

        ret=Int2ByteArray.int2Byte4(pNalu.length-4);
        writeFlvContent(ret);

        byte[] b=new byte[pNalu.length - 4];
        System.arraycopy(pNalu,4,b,0,pNalu.length-4);
        writeFlvContent(b);

        _nPrevTagSize = 11 + nDataSize;
    }
    public int ConvertH264(byte[] pNalu,int nTimeStamp)
    {
        _nTimeStamp = nTimeStamp;

        if (pNalu == null || pNalu.length <= 4)
            return 0;

        int nNaluType = pNalu[4] & 0x1f;
        //序列参数集（SPS）、图像参数集（PPS）、增强信息（SEI）
        if (_pSPS == null && nNaluType == 0x07)//序列参数集0x07
        {
            _pSPS = new byte[pNalu.length];
            _nSPSSize = pNalu.length;
            System.arraycopy(pNalu,0,_pSPS,0,pNalu.length);
        }
        if (_pPPS == null && nNaluType == 0x08)//图像参数集0x08
        {
            _pPPS = new byte[pNalu.length];
            _nPPSSize = pNalu.length;
            System.arraycopy(pNalu,0,_pPPS,0,pNalu.length);
        }
        if (_pSPS != null && _pPPS != null && _bWriteAVCSeqHeader == 0)
        {
            WriteH264Header(nTimeStamp);
            _bWriteAVCSeqHeader = 1;
        }
        if (_bWriteAVCSeqHeader == 0)
            return 1;
        WriteH264Frame(pNalu, nTimeStamp);
        return 1;
    }
    public void WriteH264EndofSeq()
    {
        byte[]a = new byte[1];
        byte[] ret=Int2ByteArray.int2Byte4(_nPrevTagSize);
        writeFlvContent(ret);

        a[0] = 0x09;
        writeFlvContent(a);
        int nDataSize;
        nDataSize = 1 + 1 + 3;

        ret=Int2ByteArray.int2Byte3(nDataSize);
        writeFlvContent(ret);

        ret=Int2ByteArray.int2Byte3(_nTimeStamp);
        writeFlvContent(ret);

        a[0]=(byte) (_nTimeStamp >> 24);
        writeFlvContent(a);

        ret=Int2ByteArray.int2Byte3(_nStreamID);
        writeFlvContent(ret);

        a[0] = 0x27;
        writeFlvContent(a);
        a[0] = 0x02;
        writeFlvContent(a);
        ret=Int2ByteArray.int2Byte3(0);
        writeFlvContent(ret);
    }
    public void WriteFlvFrame(EnumType enumType,byte[] pBuf, long timeStamp)
    {
        if(_nFirstTimeStamp == 0){
            _nFirstTimeStamp = timeStamp;
        }
        //contentType  0:视频h264帧,1:音频aac帧,2:视频头,3:音频头
        int nNaluType = 0;
        if (enumType == EnumType.VIDEO_H264) {//如果是视频h264帧
            nNaluType = pBuf[4] & 0x1f;
            if (nNaluType == 7 || nNaluType == 8) {
                if (((pBuf != null) && (pBuf[4] != 0x67 && pBuf[4] != 0x68)))
                    _nTimeStamp = (int) (timeStamp - _nFirstTimeStamp);
                return;
            }
        }
        byte[] ret=Int2ByteArray.int2Byte4(_nPrevTagSize);
        writeFlvContent(ret);//PreviousTagSize0

        byte[] a = new byte[1];
        a[0]=0;
        if (enumType == EnumType.VIDEO_H264|| enumType == EnumType.VIDEO_H264_HEADER ) {//如果是视频h264帧或视频头
            a[0] = 0x09;
        }else if (enumType == EnumType.AUDIO_AAC|| enumType == EnumType.AUDIO_AAC_HEADER) {//如果是音频aac帧或音频头
            a[0] = 0x08;
        }
        writeFlvContent(a);//TagType  ----- flv tag header

        int nDataSize=0;
        if (enumType == EnumType.VIDEO_H264) {//如果是视频h264帧
            nDataSize = 1 + 1 + 3 + 4 + (pBuf.length - 4);
        }else if (enumType == EnumType.AUDIO_AAC) {//如果是音频aac帧
            nDataSize = 1 + 1 + (pBuf.length - 7);
        }else if (enumType == EnumType.VIDEO_H264_HEADER) {//如果视频头
            nDataSize = 1 + 1 + 3 + 6 + 2 + (_nSPSSize - 4) + 1 + 2 + (_nPPSSize - 4);
        }else if (enumType == EnumType.AUDIO_AAC_HEADER) {//如果音频头
            nDataSize = 1 + 1 + 2;
        }
        ret=Int2ByteArray.int2Byte3(nDataSize);
        writeFlvContent(ret);//DataSize

        if (enumType == EnumType.VIDEO_H264|| enumType == EnumType.VIDEO_H264_HEADER) {//如果视频帧或视频头
            ret=Int2ByteArray.int2Byte3(_nTimeStamp);
            writeFlvContent(ret);//TimesStamp

            a[0] = (byte) (_nTimeStamp >> 24);
            writeFlvContent(a);//TimesStampExtended
            if ((pBuf==null )||((pBuf !=null)&&(pBuf[4] != 0x67 && pBuf[4] != 0x68)))
//                _nVideoTimeStamp += videoTimeStampGrowth;
                _nTimeStamp = (int) (timeStamp - _nFirstTimeStamp);
        }else if (enumType == EnumType.AUDIO_AAC|| enumType == EnumType.AUDIO_AAC_HEADER) {//如果是音频aac帧或音频头
            ret=Int2ByteArray.int2Byte3(_nTimeStamp);
            writeFlvContent(ret);//TimesStamp

            a[0] = (byte) (_nTimeStamp >> 24);
            writeFlvContent(a);//TimesStampExtended
            if (enumType == EnumType.AUDIO_AAC) {
//                _nTimeStamp += (double)(1024 * 1000) / (double)(44100);
                _nTimeStamp = (int) (timeStamp - _nFirstTimeStamp);
            }
        }

        ret=Int2ByteArray.int2Byte3(_nStreamID);
        writeFlvContent(ret);//StreamId


        if (enumType == EnumType.VIDEO_H264) {//如果是视频h264帧
            if (nNaluType == 5) {
                a[0] = 0x17;
                writeFlvContent(a);//Frame type : key frame, code id : avc  ----- video tags
            }else {
                a[0] = 0x27;
                writeFlvContent(a);//Frame type : inter frame, code id :  avc  ----- video tags
            }
            a[0] = 1;
            writeFlvContent(a);//AVC packet type:
            ret=Int2ByteArray.int2Byte3(0);
            writeFlvContent(ret);//composition time
            ret=Int2ByteArray.int2Byte4(pBuf.length-4);
            writeFlvContent(ret);//数据大小
            byte[] RawH264FrameData=new byte[pBuf.length-4];
            System.arraycopy(pBuf,4,RawH264FrameData,0,pBuf.length-4);
            writeFlvContent(RawH264FrameData);//数据内容
        }
        else if (enumType == EnumType.AUDIO_AAC) {//如果是音频aac帧
            byte[] cAudioParam = new byte[1];
            cAudioParam[0]=(byte) 0xAF;
            writeFlvContent(cAudioParam);// sound format : 10: aac  ,sound rate: 3 :44KHZ
            byte[] cAACPacketType = new byte[1];
            cAACPacketType[0] = 1;
            writeFlvContent(cAACPacketType); // AAC raw data -----aac audio data
            byte[] RawAACFrameData=new byte[pBuf.length-7];
            System.arraycopy(pBuf,7,RawAACFrameData,0,pBuf.length-7);
            writeFlvContent(RawAACFrameData);//Raw AAC frame data
        }
        else if (enumType == EnumType.VIDEO_H264_HEADER) {//如果是视频头
            byte[] cVideoParam =new byte[1];
            cVideoParam[0]=0x17;
            writeFlvContent(cVideoParam);//Frame type : key frame, code id : avc  ----- video tags

            byte[] cAVCPacketType =new byte[1];
            cAVCPacketType[0]=0;
            writeFlvContent(cAVCPacketType);//AVC packet type:

            ret =Int2ByteArray.int2Byte3(0);
            writeFlvContent(ret);//composition time

            a =new byte[1];
            a[0]=1;
            writeFlvContent(a);
            byte[] b=new byte[3];
            System.arraycopy(_pSPS,5,b,0,3);
            writeFlvContent(b);
            a[0] =(byte) 0xff;
            writeFlvContent(a);
            a[0] = (byte) 0xE1;
            writeFlvContent(a);

            ret=Int2ByteArray.int2Byte2(_nSPSSize - 4);
            writeFlvContent(ret);

            byte[] c=new byte[_nSPSSize - 4];
            System.arraycopy(_pSPS,4,c,0,_nSPSSize - 4);
            writeFlvContent(c);
            a[0]= 0x01;
            writeFlvContent(a);

            ret=Int2ByteArray.int2Byte2(_nPPSSize - 4);
            writeFlvContent(ret);
            byte[] d=new byte[_nPPSSize - 4];
            System.arraycopy(_pPPS,4,d,0,_nPPSSize - 4);
            writeFlvContent(d);
        }else if(enumType == EnumType.AUDIO_AAC_HEADER){//如果是音频头
            byte[] cAudioParam = new byte[1];
            cAudioParam[0]=(byte) 0xAF;
            writeFlvContent(cAudioParam);// sound format : 10: aac  ,sound rate: 3 :44KHZ

            byte[] cAACPacketType =new byte[1];
            cAACPacketType[0]=0;
            writeFlvContent(cAACPacketType);//AACPacketType  aac sequence header
            byte[] b=new byte[2];
            System.arraycopy(_pAudioSpecificConfig,0,b,0,2);
            writeFlvContent(b);// audio specific config
        }
        _nPrevTagSize = 11 + nDataSize;
    }
    public int ConvertFlv(EnumType enumType,byte[] pBuf, long nTimeStamp) {
        if (enumType == EnumType.VIDEO_H264) {//视频转换
            //System.out.println(" nalu: "+HexStrUtil.encodeHex(pBuf));
            if (pBuf == null || pBuf.length <= 4)
                return 0;

            int nNaluType = pBuf[4] & 0x1f;
            //序列参数集（SPS）、图像参数集（PPS）、增强信息（SEI）
            if (_pSPS == null && nNaluType == 0x07)//序列参数集0x07
            {
                _pSPS = new byte[pBuf.length];
                _nSPSSize = pBuf.length;
                System.arraycopy(pBuf,0,_pSPS,0,pBuf.length);
                // System.out.println("序列参数集");
            }
            if (_pPPS == null && nNaluType == 0x08)//图像参数集0x08
            {
                _pPPS = new byte[pBuf.length];;
                _nPPSSize =  pBuf.length;
                System.arraycopy(pBuf,0,_pPPS,0,pBuf.length);
                // System.out.println("图像参数集");
            }
            if (_pSPS != null && _pPPS != null && _bWriteAVCSeqHeader == 0)
            {
                //logger.info("VIDEO_H264_HEADER pre "+HexStrUtil.encodeHex(flvContentBuf,0,flvContentSize));
                WriteFlvFrame(EnumType.VIDEO_H264_HEADER,null,nTimeStamp);
                _bWriteAVCSeqHeader = 1;
                //logger.info("VIDEO_H264_HEADER end "+HexStrUtil.encodeHex(flvContentBuf,0,flvContentSize));
            }
            if (_bWriteAVCSeqHeader == 0){
                // System.out.println("未写AVCSeqHeader 返回");
                return 1;
            }
            WriteFlvFrame(EnumType.VIDEO_H264,pBuf,nTimeStamp);
            //logger.info("flvContentBuf: "+HexStrUtil.encodeHex(flvContentBuf,0,flvContentSize));
        }else if (enumType == EnumType.AUDIO_AAC) {//音频转换
            if (pBuf == null || pBuf.length <= 7)
                return 0;

            if (_pAudioSpecificConfig == null)
            {
                _pAudioSpecificConfig = new byte[2];
                _nAudioConfigSize = 2;

                _aacProfile = (pBuf[2] >> 6) + 1;
                _sampleRateIndex = (pBuf[2] >> 2) & 0x0f;
                _channelConfig = (((pBuf[2] & 0x01) << 2)&0x03ff) + ((pBuf[3] >> 6)&0x03);

                _pAudioSpecificConfig[0] = (byte) ((_aacProfile << 3) + (_sampleRateIndex >> 1));
                _pAudioSpecificConfig[1] = (byte) (((_sampleRateIndex & 0x01) << 7) + (_channelConfig << 3));
            }
            if (_pAudioSpecificConfig != null &&_bWriteAACSeqHeader == 0)
            {
                WriteFlvFrame(EnumType.AUDIO_AAC_HEADER,null,nTimeStamp);
                _bWriteAACSeqHeader = 1;
            }
            if (_bWriteAACSeqHeader == 0)
                return 1;

            WriteFlvFrame(EnumType.AUDIO_AAC, pBuf,nTimeStamp);
        }
        return 1;
    }

}
